package edu.hit.irlab.coref.util;

import java.util.HashSet;

import nlpeap.feature.basictypes.GenderType;
import nlpeap.feature.basictypes.NumberType;
import nlpeap.tms.Token;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import edu.hit.irlab.coref.CRConfigProperties;
import edu.hit.irlab.coref.mentionfinder.Mention;
import edu.hit.irlab.coref.mentionfinder.MentionAnnotation;
import edu.hit.irlab.coref.mentionfinder.MentionType;

import nlpeap.knowledge.SemanticClass;
import nlpeap.knowledge.SemanticClass.SemanticClassEnum;
import nlpeap.nlp.LinguisticConstants;

/**
 * Linguistics-related calculations <br>
 * Including whether the <i>Markable</i> is <i>ProperName</i>,
 * or is <i>Pronoun</i> and so on. <br>
 * It also calculate the {@link edu.hit.irlab.coref.mentionfinder.MentionType MentionType}}
 * for <i>Mentions</i>
 * 
 * @author chuter
 *
 */
public class LinguisticCalc {
	
	private static Log _log = LogFactory.getLog(LinguisticCalc.class.getName());
	private final CRConfigProperties _prop = CRConfigProperties.getInstance();
	private final SemanticClass _sementic_class_ins = SemanticClass.getInstance();
	
	private static LinguisticCalc _linguisticCalc_ins;
	
	public static LinguisticCalc getInstance()
	{
		if (_linguisticCalc_ins == null)
			_linguisticCalc_ins = new LinguisticCalc();
		return _linguisticCalc_ins;
	}
	
	private LinguisticCalc() {}
	
	public static void addWords2Set(String[] strArr, HashSet<String> strSet)
	{
		for (String str : strArr)
			strSet.add(str);
	}
	
	/** Gets whether the markable is a proper name or not
     * (based on) the token tag (whether match the Proper Name
     * regular expressions)
     */
    public boolean calcIsProperName(final Mention mention)  
    {
    	//check the MentionAnnotation first
    	MentionAnnotation annot = mention.getMentionAnnotType();
    	if (annot!=null && annot.getAnnotMentionSubType()!=null) 
    		return true;
    	
    	//if the mentionSubType has been set, then it sounds that
    	//it's a ProperName and with the particular mentionSubType
    	if ( mention.getMentionSubType() != null )
    		return true;
    	
    	Token[] head_token_arr = mention.getHead_Tokens();
    	//to check whether the head tag of the mention match
    	//the pattern
    	String _match_regxp = _prop.getProperNameTagRegxp();
    	for (Token token : head_token_arr)
    		if (token.getTag().matches(_match_regxp))
    			return true;
    	       
        return false;
    }
       
    /** Determine whether mention is a PRONOUN
     */
    public boolean calcPronoun(final Mention mention)
    {
        return (
        			mention.extentTokenSize() == 1
                &&
                	mention.getdoc().gettoken(mention.getExtentEndtokenId()).getContent().
                		matches(LinguisticConstants.PRONOUN)
        	);
    }
    
    /** Determine whether mention is a PER_PRONOUN
     */
    public boolean calcPerPronoun(final Mention mention)
    {
        return (
        			mention.extentTokenSize() == 1
                &&
                	mention.getdoc().gettoken(mention.getExtentEndtokenId()).getContent().
                		matches(LinguisticConstants.PER_PRONOUN)
        	);
    }
    
    /** Determine whether mention is a PER_PRONOUN
     */
    public boolean calcFirstSecondPerPronoun(final Mention mention)
    {
        return (
        			mention.extentTokenSize() == 1
                &&
                	mention.getdoc().gettoken(mention.getExtentEndtokenId()).getContent().
                		matches(LinguisticConstants.FIRSTSECOND_PER_PRONOUN)
        	);
    }
    
    /** Determine whether mention is a DEMON_PRONOUN
     */
    public boolean calcDEMPronoun(final Mention mention)
    {
        return (
        			mention.extentTokenSize() == 1
                &&
                	mention.getdoc().gettoken(mention.getExtentEndtokenId()).getContent().
                		matches(LinguisticConstants.DEMON_PRONOUN)
        	);
    }
    
    /** Checks whether a mention starts with a demonstrative */
    public boolean startsWithDemonstrative(Mention mention) 
    {
    	Token _fst_token = mention.getdoc().gettoken(mention.getExtentBegintokenId());
    	String _fst_token_cont = _fst_token.getContent();
    	if (_fst_token_cont.matches(LinguisticConstants.DEMONSTRATIVE))
    		return true;
        return false;
    }
    
    /** Determine mention's semantic class  */
    public SemanticClassEnum calcSemClass(final Mention mention)
    {
    	String head = mention.getHead();
    	if (head == null) {
    		_log.debug(String.format("the head hasn't been set well"+
    				" for the mention: ", mention.toString()));
    		return SemanticClassEnum.UNKNOWN;
    	}
    	    	
    	return _sementic_class_ins.getWordSemanticClass(head);
    }
    
    /** Checks whether a mention is a Person Name */
    public boolean calcPersonname(final Mention mention)
    {
    	Token headToken = mention.getdoc().gettoken(mention.getHeadEndtokenId());
    	if (headToken == null) {
    		_log.debug(String.format("the head token is null for this mention: ",
    				mention.toString()));
    		return false;
    	}
    	
    	// check whether the tag of the head token of the mention
    	// match the tag regular expression for Person Names 
    	if (headToken.getTag().matches(_prop.getPersonNameTagRegxp()))
    		return true;
    	
    	//if the mention has been set sub type, then it indicates
    	//that the mention is s ProperName, then check whether it's
    	//tag matches the tag regular expression for Person Names
    	String mentionSubType = mention.getMentionSubType();
    	if (
    			mentionSubType!=null 
    		&& 
    			mentionSubType.matches(_prop.getPersonNameTagRegxp())
    	)
    	{ return true; }
    	
    	return false;
    }
    
    private SemanticClassEnum getAnnotMentionType(final Mention mention) {
    	MentionAnnotation annot = mention.getMentionAnnotType();
    	if (annot != null) {
    		String type = annot.getAnnotMentionType();
    		if (type != null)
    			return SemanticClass.getFromString(type);
    	}
    	
    	return null;
    }
    
    public MentionType calcMentionType(final Mention mention) 
    {
        MentionType result = new MentionType();
        //Enumeration
        //isPropername
        if(calcIsProperName(mention))
            result.features.add(MentionType.Features.isProperName);
        
        //isDemonstrative
        if (startsWithDemonstrative(mention)) {
            result.features.add(MentionType.Features.isDemonstrative);
            if (mention.extentTokenSize() == 1) {
                result.features.add(MentionType.Features.isDemPronoun);
            } else {
                result.features.add(MentionType.Features.isDemNominal);
            }
        }
        
        //isPronoun
        if (calcPronoun(mention)) {
            result.features.add(MentionType.Features.isPronoun);
            if (calcPerPronoun(mention))
            	result.features.add(MentionType.Features.isPersPronoun);
//            else if (calcDEMPronoun(mention))
//            	result.features.add(MentionType.Features.isDemPronoun);
            else if (calcFirstSecondPerPronoun(mention))
            	result.features.add(MentionType.Features.isFirstSecondPerson);
        }
        
        //is the mention is a person name
        if (calcPersonname(mention))
        	result.features.add(MentionType.Features.isPersonName);
        	
        //the number type, is PLURAL, Singular or Unknown
        if (result.features.contains(MentionType.Features.isPersonName))
			result.number = NumberType.SINGLE; 
        else {
	        switch (NumberCalc.getInstance().calcNumberType(mention)) {
	        case PLURAL:
	        	result.number = NumberType.PLURAL;
	        	break;
	        case SINGLE:
	        	result.number = NumberType.SINGLE;
	        	break;
	        default:
	        	result.number = NumberType.UNKNOWN;
	        	break;
	        }
        }
       
        //to calculate only for PersonNames and PronNouns
        if (result.features.contains(MentionType.Features.isPersonName)
        		|| result.features.contains(MentionType.Features.isPronoun))
        	result.gender = GenderCalc.getInstance().calcGendertype(mention);
        else
        	result.gender = GenderType.UNKNOWN;
        
        //the semantic class
        //first check annotation information
        SemanticClassEnum _annotSMtype = getAnnotMentionType(mention);
        if (_annotSMtype != null)
        	result.semanticClass = _annotSMtype;
        else {
	        if (result.features.contains(MentionType.Features.isPersPronoun))
	    		result.semanticClass = SemanticClassEnum.PERSON;
			else if (result.features.contains(MentionType.Features.isPersonName)) {
	    		if (result.gender.equals(GenderType.FEMALE))
	    			result.semanticClass = SemanticClassEnum.FEMALE;
	    		else if (result.gender.equals(GenderType.MALE))
	    			result.semanticClass = SemanticClassEnum.MALE;
	    	} else
	    		result.semanticClass = calcSemClass(mention);
        }
        
        return result;
    }
    
}
